using System.Net;
using System.Net.Sockets;

namespace Pinwheel.Kernel
{
    public class ServerSocket : IServerSocket, IDisposable
    {
        public const int DefaultCount = 4096;
        public const int DefaultBufferSize = 128 * 1024;    // 128KB
        // public const int OpsToPreAlloc = 2;

        int m_port;
        int m_maxCount;
        int m_bufferSize;

        Socket m_socket;
        IPEndPoint m_point;
        
        // BufferManager m_bufferManager;                      // 缓冲区管理器
        SocketAsyncEventArgsPool m_socketEventPool;         // 事件池，用于重用 SocketAsyncEventArgs
        SemaphoreSlim m_maxClients;                         // 控制最大客户端连接数
        int m_numConnectedSockets;                          // the total number of clients connected to the server

        ISocketListener m_listener;                         // Socket监听器
        // ISocketSessionProvider m_sessionProvider;           // Socket令牌提供器

        #region Constructors
        
        public ServerSocket(ISocketListener listener) : this(listener, ProtocolType.Tcp, DefaultCount, DefaultBufferSize)
        {
        }
        
        public ServerSocket(ISocketListener listener, bool dual) : this(listener, ProtocolType.Tcp, DefaultCount, DefaultBufferSize, dual)
        {
        }
        
        public ServerSocket(ISocketListener listener, int count, int size) : this(listener, ProtocolType.Tcp, count, size)
        {
        }
        
        public ServerSocket(ISocketListener listener, int count, int size, bool dual) : this(listener, ProtocolType.Tcp, count, size, dual)
        {
        }
        
        public ServerSocket(ISocketListener listener, ProtocolType protocolType, int count, int size, bool dual = false)
        {
            m_listener = listener;
            
            var addressFamily = dual ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
            m_socket = new Socket(addressFamily, SocketType.Stream, protocolType);
            if (dual) m_socket.DualMode = dual;
            m_maxCount = count;
            m_bufferSize = size;
            
            m_maxClients = new SemaphoreSlim(m_maxCount, m_maxCount); 
            // m_bufferManager = new BufferManager(m_maxCount * m_bufferSize * OpsToPreAlloc, m_bufferSize);
            m_socketEventPool = new SocketAsyncEventArgsPool(m_maxCount);
            
            Init();
        }
        
        public void Init()
        {
            // m_bufferManager.InitBuffer();
            // 初始化 SocketAsyncEventArgs 对象池
            for (int i = 0; i < m_maxCount; i++)
            {
                var receiveEventArgs = new SocketAsyncEventArgs();
                receiveEventArgs.Completed += OnIOCompleted;
                // m_bufferManager.SetBuffer(receiveEventArgs);
                
                var sendEventArgs = new SocketAsyncEventArgs();
                sendEventArgs.Completed += OnIOCompleted;
                // m_bufferManager.SetBuffer(sendEventArgs);
                
                var session = new SocketSession(this, m_listener, receiveEventArgs, sendEventArgs, DefaultBufferSize);
                receiveEventArgs.SetBuffer(session.Received.Buffer, 0, session.Received.Free);
                m_socketEventPool.Push(receiveEventArgs);
            } 
        }
        
        #endregion
        

        #region Listen

        public void Listen(int port)
        {
            m_port = port;
            m_point = m_socket.DualMode ? new IPEndPoint(IPAddress.IPv6Any, m_port) : new IPEndPoint(IPAddress.Any, m_port);
            m_socket.Bind(m_point);
            m_socket.Listen(m_maxCount);
            StartAccept(null!);
            Log.Info($"[Socket] Server listen on {m_point.Address}:{m_point.Port}.");
        }
        
        public void Listen(IPEndPoint endPoint)
        {
            m_port = endPoint.Port;
            m_point = endPoint;
            m_socket.Bind(m_point);
            m_socket.Listen(m_maxCount);
            StartAccept(null!);
            Log.Info($"[Socket] Server listen on {m_point.Address}:{m_point.Port}.");
        } 

        #endregion

        
        #region Accept

        private void StartAccept(SocketAsyncEventArgs eventArg)
        {
            // 第一次连接，创建用于连接SocketAsyncEventArgs的对象，注册连接完成方法
            if (eventArg == null!)
            {
                eventArg = new SocketAsyncEventArgs();
                eventArg.Completed += OnAcceptCompleted;
            }
            //不是第一次连接，将AcceptSocket=null，方便再次连接获取新的AcceptSocket对象
            else
            {
                eventArg.AcceptSocket = null;
            }

            m_maxClients.Wait();

            bool willRaiseEvent = m_socket.AcceptAsync(eventArg);
            if (!willRaiseEvent)
            {
                ProcessAccept(eventArg);
            }
        }
        
        private void OnAcceptCompleted(object sender, SocketAsyncEventArgs acceptEventArgs)
        {
            ProcessAccept(acceptEventArgs);
        }
        
        private void ProcessAccept(SocketAsyncEventArgs acceptEvent)
        {
            if (acceptEvent.SocketError != SocketError.Success)
            {
                Error(acceptEvent);
                return;
            }
            
            // 获取客户端 Socket
            Socket socket = acceptEvent.AcceptSocket!;
            if (socket.Connected)
            {
                // 客户端连接个数原子操作加1
                Interlocked.Increment(ref m_numConnectedSockets);
                var socketEvent = m_socketEventPool.Pop();
                
                // 设置Socket
                var session = (ISocketSession) socketEvent.UserToken!;
                session.Accept(socket);
                
                // 开始接收数据
                StartReceive(socketEvent);
            }

            // 投递下一个接受请求
            StartAccept(acceptEvent);
        } 

        #endregion


        private void OnIOCompleted(object? sender, SocketAsyncEventArgs socketEvent)
        {
            switch (socketEvent.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(socketEvent);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(socketEvent);
                    break;
            }
        }

        #region Receive

        public void StartReceive(SocketAsyncEventArgs socketEvent)
        {
            Socket socket = socketEvent.AcceptSocket;
            Assert.Check(null != socket, "[Socket] StartReceive socket is null."); 
            try
            {
                if (!socket.ReceiveAsync(socketEvent))
                {
                    ProcessReceive(socketEvent);
                }
            }
            catch (SocketException e)
            {
                Log.Exception(e);
            }
        }

        public void ProcessReceive(SocketAsyncEventArgs socketEvent)
        {
            // 如果接收到的字节数为 0，表示远程端已经关闭连接
            if (socketEvent.SocketError != SocketError.Success || socketEvent.BytesTransferred <= 0)
            {
                Error(socketEvent);
                return;
            }
            
            var session = (ISocketSession) socketEvent.UserToken;
            Assert.Check(null != session, "[Socket] ProcessReceive token is null.");
            
            Socket socket = socketEvent.AcceptSocket;
            Assert.Check(null != socket, "[Socket] ProcessReceive socket is null.");
            
            // Log.Info($"[Socket] Receive [{socket.RemoteEndPoint}] {socketEvent.BytesTransferred} bytes data.");
            // token.Received.Push(socketEvent.Buffer, socketEvent.Offset, socketEvent.BytesTransferred);
            // token.Received.Push(socketEvent.BytesTransferred);
            session.Receive(socket);
            
            StartReceive(socketEvent);
        } 

        #endregion


        #region Send

        public void StartSend(SocketAsyncEventArgs socketEvent)
        {
            Socket socket = socketEvent.AcceptSocket;
            Assert.Check(null != socket, "[Socket] Send socket is null.");
            
            // var session = (ISocketSession) socketEvent.UserToken;
            // Assert.Check(null != session, "[Socket] StartSend session is null.");
            
            // if (session.Sent.CurSize <= 0) return;
            // var size = session.Sent.Pop(socketEvent.Buffer, socketEvent.Offset, m_bufferSize);
            // socketEvent.SetBuffer(socketEvent.Offset, size);

            bool willRaiseEvent = socket.SendAsync(socketEvent);
            if (!willRaiseEvent)
            {
                ProcessSend(socketEvent);
            }
        }
        
        private void ProcessSend(SocketAsyncEventArgs socketEvent)
        {
            if (socketEvent.SocketError != SocketError.Success)
            {
                Error(socketEvent);
                return;
            }
            
            var session = (ISocketSession) socketEvent.UserToken;
            Assert.Check(null != session, "[Socket] Send session is null.");
            session.Sended(socketEvent.Count);
            
            if (session.Prepare.ReadCount > 0)
                StartSend(socketEvent);
        } 

        #endregion

        private void Error(SocketAsyncEventArgs socketEvent)
        {
            var session = (ISocketSession) socketEvent.UserToken!;
            if (null! != session)
            {
                session.Error(socketEvent.AcceptSocket!, socketEvent.SocketError);
            }
            Close(socketEvent);
        }

        public void Close(SocketAsyncEventArgs socketEvent)
        {
            var socket = socketEvent.AcceptSocket;
            if (null == socket || !socket.Connected) return;
            // Assert.Check(null != socket, "[Socket] Close socket is null.");
            // Assert.Check(socket.Connected, "[Socket] Try close socket without connect.");

            Interlocked.Decrement(ref m_numConnectedSockets);
            var session = (ISocketSession) socketEvent.UserToken!;
            Assert.Check(null != session, "[Socket] Close socket with null session.");

            session.Close(socket);
            m_socketEventPool.Push(socketEvent);
            m_maxClients.Release();

            Log.Info($"[Socket] {socket.RemoteEndPoint} connection close.");
            
            try
            {
                if (socket.Connected)
                    socket.Shutdown(SocketShutdown.Both);
            }
            catch (SocketException e)
            {
                Log.Exception(e);
            }
            finally
            {
                socket.Close();
            }
        }

        public void Close()
        {
            try
            {
                if (m_socket.Connected)
                    m_socket.Shutdown(SocketShutdown.Both);
            }
            catch (Exception e)
            {
                Log.Exception(e);
            }
            finally
            {
                m_socket.Close();
            }
        }

        public void Dispose()
        {
            Close();
            // m_bufferManager = null!;
            m_socket.Dispose();
            m_socket = null!;
        }
    }
}